package edu.ufl.cise.cop5555.sp12;

import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;

import org.w3c.dom.stylesheets.LinkStyle;


public class TokenStream {
	
	
	//This inner class provides an Iterator over the recognized tokens.  
	//Instances cannot be created directly (the constructor is private); use the public iterator() method.
	public class TokenStreamIterator implements Iterator<Token>{
		
		int i = 0;
		
		private TokenStreamIterator(){
			super();
		}
		
		@Override
		public boolean hasNext() {
			return i< tokens.size();
		}

		@Override
		public Token next() {
			return tokens.get(i++);
		}

		@Override
		public void remove() {
			throw new UnsupportedOperationException();			
		}		
		
	}
	
	

	
	//This method returns a new TokenStreamIterator for this TokenStream instance.
	public TokenStreamIterator iterator(){
		return new TokenStreamIterator();
	}

	//This exception may be thrown when there are errors in a String Literal
	@SuppressWarnings("serial")
	public class IllegalStringLiteralException extends Exception {
		public IllegalStringLiteralException(String msg) {
			super(msg);
		}
	}

	char[] inputChars;
	public final List<Token> tokens;
	public int[] lineBreaks;
	public int lineBrkNum;
	
	public void displayTokens()
	{
	Iterator i = tokens.iterator();
	Token temp;
	while(i.hasNext())
	{
		temp = (Token) i.next();
		/*System.out.println("KIND " + temp.kind);
		System.out.println(temp.beg);
		System.out.println(temp.end);
		System.out.println("Line no:" +temp.getLineNumber());
	*/	
		
	}
		
		
	}
	
		//constructor that takes an array of chars
	public TokenStream(char[] inputChars) {
		this.inputChars = inputChars;
		tokens = new ArrayList<Token>();
		
		
		//System.out.println("Line brek 1"+lineBrkNum);
	    //TODO: complete constructor if necessary
		getEolIndices();
	}

	//constructor that takes a Reader.  
	//Pass this a FileReader to read input from a file.
	public TokenStream(Reader r) {
		
		this.inputChars = getChars(r);
		tokens = new ArrayList<Token>();
		getEolIndices();
		//System.out.println("Lin e brek2 "+lineBrkNum);
	    //TODO: complete constructor if necessary
	}

	//constructor that takes a String.
	public TokenStream(String inputString) {
	
		int length = inputString.length();
		inputChars = new char[length];
		inputString.getChars(0, length, inputChars, 0);
		tokens = new ArrayList<Token>();
		
		getEolIndices();
	    //TODO: complete constructor if necessary
	}


	//utility method
	//read all the characters from the given Reader into a char array.
	private char[] getChars(Reader r) {
		StringBuilder sb = new StringBuilder();
		try {
			int ch = r.read();
			while (ch != -1) {
				
				sb.append((char) ch);
				ch = r.read();
			}
		} catch (IOException e) {
			throw new RuntimeException("IOException");
		}
		char[] chars = new char[sb.length()];
		sb.getChars(0, sb.length(), chars, 0);
		return chars;
	}

	
	//Helper method to get the eol Indices and store in Array..Call this method in the constructor
	public boolean getEolIndices()
	{
		int sizeOfInput = inputChars.length;
	//	System.out.println("size : "+sizeOfInput);
		char curr;
		int j=0;
		int tempInd = 0;
		ArrayList<Integer> tempList = new ArrayList<Integer>();
		
		for(int i=0;i<sizeOfInput;i++)
		{
			
			curr = inputChars[i];
			switch(curr)
			{
			
			case '\n' :
				tempList.add((Integer)i);
				break;
			case '\r' : 
				tempList.add(i);
				if(i+1 < sizeOfInput && inputChars[i+1] == '\n')
				{
					//tempList.add(i);
					i++;
				}
				
			
				
				break;
			case '\u0085': case  '\u2028' : case '\u2029' :
				tempList.add(i);
				break;			
				
					
				
			}
		}// end for
		Iterator<Integer> it = tempList.iterator();
		lineBrkNum = tempList.size();
		lineBreaks = new int[lineBrkNum];
		int i = 0;
		while (it.hasNext ()) {
		   Integer num = (Integer) it.next ();
		   lineBreaks[i++] = (int) num; 
		//   System.out.print(num+" " );
		  }
	//	System.out.println("\n");
		
	/*	for(int i=0;i<sizeOfInput;i++)
		{
			curr = inputChars[i];
			if (curr == '\\') //check if it starts with '\'
			{
				tempInd = i;
				char ch = inputChars[++i]; //get next char
				switch (ch) { // A line termination character sequence is a character or character pair
								//from the set: \n, \r, \r\n, \u0085, \u2028, and \u2029.
				 
				case 'n':
					tempList.add((Integer)tempInd);
					break;
				case 'r':
					if(i+2 < sizeOfInput)
					{
					char c1 = inputChars[i+1];		
					if(c1 == '\\')
					{
						if(inputChars[i+2] == 'n')
						{
							i+=2;
							tempList.add((Integer)tempInd);
							
						}
					
					}
					
					else
						tempList.add((Integer)tempInd);
					}
					else

						tempList.add((Integer)tempInd);
					break;
				case 'u':
					
					StringBuilder sb = new StringBuilder();
					if(i+4 < sizeOfInput)
					{
					sb.append(inputChars[i+1]);
					sb.append(inputChars[i+2]);
					sb.append(inputChars[i+3]);
					sb.append(inputChars[i+4]);
					String s = sb.toString();
					if(s.equals("0085"))
					{
						i+=4;
						tempList.add((Integer)tempInd);
					}
					else if (s.equals("2028"))
					{
						i+=4;
						tempList.add((Integer)tempInd);
					}
					else if (s.equals("2029"))
					{
						i+=4;
						tempList.add((Integer)tempInd);	
					}
					
					
					}
					break;
				default:
					
				}
			} 
		}//end for
		
		Iterator<Integer> it = tempList.iterator();
		lineBrkNum = tempList.size();
		lineBreaks = new int[lineBrkNum];
		int i = 0;
		while (it.hasNext ()) {
		   Integer num = (Integer) it.next ();
		   lineBreaks[i++] = (int) num; 
		   System.out.println(num+" ---" );
		  }
		*/
		return true;	
		}
	
		
	
	

	// This is a non-static inner class. Each instance is implicitly linked with an
	// instance of StreamToken and can access that instance's variables.
	// Note usage:
	//     TokenStream stream = ...
	//     Token t = stream.new Token(....)

	public class Token {
		public final Kind kind;
		public final int beg;
		public final int end;
		public int scopeNum;
		
		public void setScope(int scope)
		{
			scopeNum = scope;
		}
		public int getScope()
		{
			return scopeNum;
		}
		

		public Token(Kind kind, int beg, int end) {
			this.kind = kind;
			this.beg = beg;
			this.end = end;
		}

		// this should only be applied to Tokens with kind==INTEGER_LITERAL
		public int getIntVal() throws NumberFormatException,
				IllegalStringLiteralException {
			assert kind == Kind.INTEGER_LITERAL : "attempted to get value of non-number token";
			return Integer.valueOf(getText());
		}

		// removes quotes and handles escapes
		public String getStringVal() throws IllegalStringLiteralException {
			assert kind == Kind.STRING_LITERAL : "attempted to get string value of non-string token";
			StringBuilder sb = new StringBuilder();
			assert inputChars[beg] == '"' && inputChars[end - 1] == '"' : "malformed STRING_LITERAL token";

			for (int i = beg + 1; i < end - 1; i++) {
				char curr = inputChars[i];
				if (curr == '\\') {
					char ch = inputChars[++i];
					switch (ch) { // \ b | \n | \f | \r | \" | \\
					case 't':
						sb.append('\t');
						break;
					case 'n':
						sb.append('\n');
						break;
					case 'f':
						sb.append('\f');
						break;
					case 'r':
						sb.append('\r');
						break;
					case '\"':
						sb.append('\"');   //note that the quote is escaped for Java.  Only the " will be appended
						break;
					case '\\':
						sb.append('\\');   //note that the \ is escaped for Java.  Only one \ will be appended.
						break;
					default:
						throw new IllegalStringLiteralException(
								"attempted to escape ch in String");
					}
				} else
					sb.append(curr);
			}
			return sb.toString();
		}

		
		public int getLineNumber() {
			/*
			 * A line termination character sequence is a character or character pair
			 * from the set: \n, \r, \r\n, \u0085, \u2028, and \u2029.
			 */
			// Do a binary search using beg and end and find line number
			//Arrays a = new Arrays();
		
			
			
			int pos = Arrays.binarySearch(lineBreaks, end);
		//	System.out.println("pos "+ pos);
			if(pos >= 0 ) // end is found
				return pos+1;
			else  // end was not found
			{
				pos = pos+1;
				int ln = (pos* -1 )+1;
				return ln;
			}
		
			
			

			
		}
		
		
		// returns string containing raw text of token. 
		//precondition: beg <= end, end < inputChars.length
		public String getRawText() {
			assert (beg <= end &&  end <= inputChars.length):"called getRatText with invalid beg and end";
			return String.valueOf(inputChars, beg, end - beg);
		}

		// converts to String, if String literal, removes delimiting "s and handles escapes.
		// if token is error, surrounds text with %# #%, if EOF, the text is EOF.
		public String getText() throws IllegalStringLiteralException {
			if (kind == Kind.STRING_LITERAL) {
				return getStringVal();
			}
			if (kind.isError()) {
				return "%#" + getRawText() + "%#";
			}
			if (kind == Kind.EOF) {
				return "EOF";
			}
			return getRawText();
		}

		public String toString() {
			return (new StringBuilder("<").append(kind).append(",")
					.append(getRawText()).append(",").append(beg).append(",")
					.append(end).append(">")).toString();
		}

		public boolean equals(Object o) {
			if (!(o instanceof TokenStream.Token))
				return false;
			Token other = (Token) o;
			try {
				return kind.equals(other.kind) && getText().equals(other.getText());
			} catch (IllegalStringLiteralException e) {
				e.printStackTrace();
				return false;
			}
		}
	}
}
